#ifndef __CAMERA_H__
#define __CAMERA_H__

#include "ControlledObject.h"
#include "Plane.h"
#include "BoundingVolume.h"

namespace Nezha
{

	typedef Rectf ViewPortf;

	enum FrustumPlane
	{
		FP_LEFT = 0,
		FP_RIGHT,
		FP_TOP,
		FP_BOTTOM,
		FP_NEAR,
		FP_FAR,

		FP_NUM,

		FP_NONE
	};

	struct NEZHA_ENGINE_ENTRY FrustumDef
	{
		FrustumDef(const ViewPortf& vp, float _near, float _far)
		{
			mLeft = vp.LeftCorner.x;
			mRight = vp.RightCorner.x;
			mTop = vp.LeftCorner.y;
			mBottom = vp.RightCorner.y;
			mNear = _near;
			mFar = _far;
		}

		FrustumDef()
			:mLeft(0)
			,mRight(0)
			,mTop(0)
			,mBottom(0)
			,mNear(0)
			,mFar(0)
		{
		}

		float mLeft;
		float mRight;
		float mTop;
		float mBottom;
		float mNear;
		float mFar;
	};

	enum DepthType
	{
		DT_DX = 0,		// 0, 1
		DT_OGL,			// -1, 1
	};

	class Camera;

	/** a struct for caching model space frustum planes(world space). */
	class NEZHA_ENGINE_ENTRY FrustumPlanes
	{
	public:
		FrustumPlanes();
		~FrustumPlanes();

		void setCamera(const Camera* cam);

		inline void vector2plane(const Vector4& v, Plane& p)
		{
			p.mNormal.set(v.x, v.y, v.z);
			p.mConstant = v.w;
		}

		const Plane& getPlane(FrustumPlane fp) const;
	protected:
		const Camera* mCam;
		Plane mPlanes[FP_NUM];
	};

	/**
	@brief
		Default camera:
			Eye = 0, 0, 0
			Direction = 0, 0, -1
			Up = 0, 1, 0
			Right = 1, 0, 0

	*/
	class NEZHA_ENGINE_ENTRY Camera : public ControlledObject
	{
	public:
		Camera(const _string& name);
		Camera(const _string& name, bool perspective);

		virtual ~Camera();

		// override this for correctly runtime type info.
		virtual int getSceneObjectType() const
		{
			return SOT_CAMERA;
		}

		bool getPerspective() const
		{
			return mPerspective;
		}

		void setPosition(const Vector3& pos);

		void setDirection(const Vector3& dir);

		void setLookAt(const Vector3& target);

		void rotate(const Vector3& axis, float angle);

		void setOrientation(const Quaternion& quat);

		void setFrustum(const FrustumDef& f);

		/**
		@REMARKS
			fovY : degree.
		*/
		void setFrustum(float fovY, float aspect, float _near, float _far);

		void getFrustumPlaneViewSpace(FrustumPlane fp, Plane& pl);

		bool windowPoint2Ray(int x, int y, Vector3& rayOrig, Vector3& rayDir);

		/**
		@return
			if not culled, return FP_NONE
		*/
		FrustumPlane cull(const Vector3& center, float radius, FrustumPlanes& frustumPlanes) const; 

		FrustumPlane cull(const AABB& bound, FrustumPlanes& frustumPlanes) const;

		const Vector3& getPosition() const
		{
			return mPosition;
		}

		Vector3 getDirection() const;

		Vector3 getUp() const;

		Vector3 getRight() const;

		const Quaternion& getOrientation() const
		{
			return mQuat;
		}

		const FrustumDef& getFrustumDef() const
		{
			return mFrustumDef;
		}

		const Matrix4x4& getViewMat() const
		{
			return mViewMat;
		}

		const Matrix4x4& getProjectionMat() const
		{
			return mProjectionMat;
		}

		const Matrix4x4& getProjectionViewMat() const
		{
			return mPVMat;
		}

		static DepthType GetDepthType();

		void setCullingMask(u32 cullingMask);

		u32 getCullingMask() const
		{
			return mCullingMask;
		}

		const _string& getName() const
		{
			return mName;
		}

NEZHA_INTERNAL:
		void _updateViewMatrix();
		void _updateProjectionMatrix();
		void _setPerspective(bool b);

		// only dispacthed by renderer implement
		static void _SetDepthType(DepthType dt);

	private:
		_string mName;

	protected:
		// frame
		Vector3 mPosition;
		//Vector3 mDirection;
		//Vector3 mUp;
		//Vector3 mRight;
		mutable Quaternion mQuat;

		FrustumDef mFrustumDef;

		// Matrixs
		Matrix4x4 mProjectionMat;
		Matrix4x4 mPVMat;
		Matrix4x4 mViewMat;

		static DepthType msDepthType;

		u32 mCullingMask;

		bool mPerspective;
		mutable bool mOrientationDirty;
	};

}//end namespace Nezha

#endif //end __CAMERA_H__